from typing import *
from decimal import Decimal

import pandas as pd
from pyecharts.charts import Line
import pyecharts.options as opts

Number = Union[float, int, Decimal]


class ApproximationPercentFilter:
    """
    百分比过滤近似曲线计算
    """

    @staticmethod
    def extremum(data: Sequence[Number]) -> List[Tuple[int, Number]]:
        """
        获取极值
        :param data:
        :return:
        """
        result: List[Tuple[int, Number]] = [(0, data[0])]
        for i, v in enumerate(data):
            if i == 0 or i == len(data) - 1:
                continue

            if not ((v > data[i - 1]) ^ (v > data[i + 1])):
                result.append((i, v))

        result.append((len(data) - 1, data[-1]))
        return result

    @staticmethod
    def filter_percent(data: Sequence[Number], percent: Decimal = Decimal('0.05')) -> List[Tuple[int, Number]]:
        """
        百分比滤波
        :param data:
        :param percent:
        :return:
        """
        if len(data) < 3:
            raise Exception("长度过低")
        result: List[Tuple[int, Number]] = [(0, data[0])]
        # 上上个有效点的下标
        backend_index_twice: int = 0
        # 上个有效点的下标
        backend_index: int = 0

        for i, v in enumerate(data):
            if i == 0 or i == len(data) - 1:
                continue

            backend_diff = abs(v - data[backend_index])
            # 前后值变化量达到阈值
            if backend_diff > percent * data[backend_index]:
                backend_index_twice = backend_index
                backend_index = i
                result.append((i, v))
            # 当次走向和上次走向相同
            elif i > 2 and (v - data[backend_index]) * (data[backend_index] - data[backend_index_twice]) > 0:
                backend_index_twice = backend_index
                backend_index = i
                result.append((i, v))

        result.append((len(data) - 1, data[-1]))
        return result

    @staticmethod
    def filter_straight(data: Sequence[Number]) -> List[Tuple[int, Number]]:
        """
        过滤同向数据
        :param data:
        :return:
        """
        if len(data) < 3:
            raise Exception("长度过低")
        result: List[Tuple[int, Number]] = [(0, data[0])]
        front_index: int = 0
        for i, v in enumerate(data):
            if i == 0 or i == len(data) - 1:
                continue

            if (v - data[front_index]) * (data[i + 1] - v) < 0:
                front_index = i
                result.append((i, v))
        result.append((len(data) - 1, data[-1]))
        return result

    @classmethod
    def calculate(cls, x: Sequence[Any], data: Sequence[Number], render_path: str = None) -> List[Optional[Number]]:
        resp_ext = cls.extremum(data)
        resp_per = cls.filter_percent([x[1] for x in resp_ext], percent=Decimal('0.05'))
        resp_per = [(resp_ext[x[0]][0], x[1]) for x in resp_per]
        resp_straight = cls.filter_straight([x[1] for x in resp_per])
        resp_straight = [(resp_per[x[0]][0], x[1]) for x in resp_straight]
        resp_ext_map = {x[0]: x[1] for x in resp_ext}
        resp_per_map = {x[0]: x[1] for x in resp_per}
        resp_straight_map = {x[0]: x[1] for x in resp_straight}
        if render_path is not None:
            line = Line()
            line.add_xaxis(x)
            line.add_yaxis('data', data, is_symbol_show=False)
            line.add_yaxis('extr', [resp_ext_map.get(x, None) for x in range(len(data))],
                           is_connect_nones=True, linestyle_opts=opts.LineStyleOpts(color='black', width=2),
                           itemstyle_opts=opts.ItemStyleOpts(color="black"))
            line.add_yaxis('formatter_percent', [resp_per_map.get(x, None) for x in range(len(data))],
                           is_connect_nones=True, linestyle_opts=opts.LineStyleOpts(color='red', width=2),
                           itemstyle_opts=opts.ItemStyleOpts(color="red"))
            line.add_yaxis('formatter_straight', [resp_straight_map.get(x, None) for x in range(len(data))],
                           is_connect_nones=True, linestyle_opts=opts.LineStyleOpts(color='red', width=2),
                           itemstyle_opts=opts.ItemStyleOpts(color="red"))
            line.set_global_opts(
                xaxis_opts=opts.AxisOpts(is_scale=True),
                yaxis_opts=opts.AxisOpts(
                    is_scale=True,
                    splitarea_opts=opts.SplitAreaOpts(),
                ),
                datazoom_opts=opts.DataZoomOpts(type_="inside"),
                tooltip_opts=opts.TooltipOpts(trigger='axis', axis_pointer_type='cross')
            )
            line.render(render_path)
        return [resp_straight_map.get(x, None) for x in range(len(data))]


if __name__ == '__main__':
    from utils.data.sqlLoader import PgLoader

    pg = PgLoader(1672502400, 1672502400 + 3600 * 24 * 30, symbols=['BTCUSDT'])
    df = pg.fetch(1672502400 + 3600 * 24 * 30, 5000)
    df = df[df['trade_type'] == 0]
    df.sort_values(by='open_time', inplace=True)
    close_data = df['close'].tolist()
    ApproximationPercentFilter.calculate(pd.to_datetime(df['open_time'], unit='ms').strftime('%Y-%m-%d HH:MM:SS'),
                                         close_data,
                                         render_path=r'C:\work\code\backtest\locals/test.html')
